package org.nanotek.lucene.index.manage;
/**
 * Base IndexService for Lucene Query Processing.
 * Include methods to manage a index based on persitent objects.
 * Index and IndexConfig.
 */
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.util.CharArraySet;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.SimpleFSDirectory;
import org.apache.lucene.util.Version;
import org.nanotek.cms.domain.lucene.BaseDocument;
import org.nanotek.cms.domain.lucene.Index;
import org.nanotek.cms.domain.lucene.IndexHolder;
import org.nanotek.cms.domain.lucene.LuceneIndexWriterHolder;
import org.nanotek.dao.DAO;
import org.nanotek.lucene.IndexConfig;
import org.nanotek.lucene.LuceneDocumentBuilder;
import org.nanotek.lucene.index.search.AnalyzerType;
import org.nanotek.lucene.index.search.IndexType;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class LuceneIndexWriteService<T extends Object> implements IndexWriterService<T,IndexWriter>,  ApplicationContextAware{

	private DAO<Index> indexDAO;
	private Index currentIndex;
	private IndexWriter indexWriter;
	private ApplicationContext applicationContext;
	private LuceneIndexWriterHolder indexWriterRef;
	private LuceneDocumentBuilder<T> documentBuilder;
	private Boolean started = false;

	public  LuceneIndexWriteService(){}

	public  LuceneIndexWriteService (Index index)
	{
		initService (index);
	}

	public  LuceneIndexWriteService (Index index, LuceneDocumentBuilder<T> documentBuilder)
	{
		initService (index , documentBuilder);
	}

	public void initService(Index index) {
		initService (index , null);
	}

	public void initService(Index index , LuceneDocumentBuilder<T> documentBuilder) {
		if (documentBuilder !=null)
			this.documentBuilder = documentBuilder;
		else
			this.documentBuilder = (LuceneDocumentBuilder<T>) applicationContext.getBean("baseDocumentBuilder");
		indexWriterRef = (LuceneIndexWriterHolder) getIndexHolder(index);
		indexWriter = indexWriterRef.getTargetIndex();
		currentIndex = index;
		started = true;
	}

	public void shutDownService() {
		try{
			if (started == true)
			{
				indexWriter.commit();
				indexWriter.close();
			}
		} catch (Exception e) {
			throw new IndexServiceException(e);
		}finally {
			indexWriter = null;
			indexWriterRef = null;
			currentIndex = null;
			started = false;
		}
	}

	public IndexWriter createIndexWriter(Index index) throws Exception
	{
		IndexConfig indexConfig = createIndexConfig (index);
		return createIndexWriter (index, indexConfig);
	}

	public IndexWriter createIndexWriter(Index index , IndexConfig indexConfig) throws Exception
	{
		if (applicationContext == null)
			throw new RuntimeException ("Service class not configured");
		if (index !=null && indexConfig !=null) {
			IndexWriterConfig config = prepareIndexWriterConfig(index, indexConfig);
			Directory directory = prepareDirectory(index , indexConfig);
			//TODO: Check Index Open Mode.
			if (IndexWriter.isLocked(directory))
			{
				System.err.println("DIRECTORY IS LOCKED");
				config.setOpenMode(OpenMode.CREATE_OR_APPEND);
			}
			//			if (index !=null)
			//				indexWriterRef = (LuceneIndexWriterHolder) getIndexHolder(index);
			updateIndexRecord(index);
			indexWriter = (IndexWriter) applicationContext.getBean("indexWriter", new Object[]{directory,config});
		} else {
			indexWriter = (IndexWriter) applicationContext.getBean("indexWriter");
		}
		return indexWriter;
	}

	private IndexConfig createIndexConfig(Index index) {
		IndexConfig indexConfig  = new IndexConfig();
		indexConfig.setId(index.getId());
		indexConfig.setAnalyzerType(index.getAnalyzerType());
		indexConfig.setIndexPath(new File(index.getLocation().getHref()).toPath());
		indexConfig.setIndexType(index.getIndexType());
		indexConfig.setOpenMode(OpenMode.CREATE);
		indexConfig.setVersion(Version.LUCENE_40);
		return indexConfig;
	}

	private void updateIndexRecord(Index index) {
		Date indexStartDate = new Date();
		index.setDateCreated(indexStartDate);
		index.setDateUpdated(indexStartDate);
		indexDAO.update(index);
	}

	public void indexDocument (T documentSource)
	{
		Document document = null;
		try {
			if (indexWriter !=null ){
				document = documentBuilder.buildDocument(documentSource);
				indexWriter.addDocument(document);
			} else {
				throw new IndexServiceException("Index Writer Not configured");
			}
		} catch (IOException e)
		{
			throw new IndexServiceException(e);
		}finally {
			if (started = true && document !=null)
				saveIndexHistory(document);
		}
	}

	private void saveIndexHistory(Document luceneDocument)  {
		//TODO: Perform media details change on history.
		try {
			if (luceneDocument.get("id") !=null) {
				Index index = currentIndex;
				DAO<BaseDocument> baseDocumentDAO = (DAO<BaseDocument>) applicationContext.getBean("baseDocumentDAO");
				BaseDocument baseDocument = new BaseDocument();
				baseDocument.setId(luceneDocument.get("id"));
				baseDocument.setReferURL(luceneDocument.get("referURL"));
				baseDocument.setDateModified(new Date());
				baseDocumentDAO.persist(baseDocument);
			}
		} catch (Exception e) {
			throw new ServiceException(e);
		}
	}

	private Directory prepareDirectory(Index index, IndexConfig indexConfig) {
		Directory directory = null;
		if (indexConfig!=null && index !=null) {
			File file = indexConfig.getIndexPath().toFile();
			try {
				IndexType indexType = indexConfig.getIndexType();
				Class<? extends Directory> clazz = indexType.getClazz();
				Constructor<? extends Directory> constructor;
				if (indexType.equals(IndexType.SIMPLEFS) ||  indexType.equals(IndexType.NMAPDIRECTORY) || indexType.equals(IndexType.NIOFSDIRECTORY))
				{
					constructor = clazz.getConstructor(new Class<?>[]{File.class});
					directory = constructor.newInstance(new Object[]{file});
				}else
				{
					directory = new SimpleFSDirectory(file);
				}
			} catch (Exception e) {
				throw new IndexServiceException(e);
			}
		}else {
			throw new IndexServiceException("Wrong parameters configuration");
		}
		return directory;
	}

	private IndexWriterConfig prepareIndexWriterConfig(Index index,
			IndexConfig indexConfig) throws SecurityException, IllegalArgumentException, ReflectiveOperationException {
		Analyzer analyzer;
		IndexWriterConfig config = null;
		try {
			analyzer = constructAnalyzer (indexConfig);
			config = new IndexWriterConfig(indexConfig.getVersion() , analyzer);
			config.setOpenMode(indexConfig.getOpenMode());
		} catch (InstantiationException | IllegalAccessException e) {
			throw e;
		}
		return config;
	}

	//TODO: Remove assertions when test cases finishes, if possible change to a BeanFactory using Spring.
	//TODO: Implement all analyzer constructor methods.
	private Analyzer constructAnalyzer(IndexConfig indexConfig) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
		Object analyzer = null;
		Constructor<? extends Analyzer> constructor;
		Class<? extends Analyzer> analyzerClass = (indexConfig.getAnalyzerType()!=null)?AnalyzerType.STANDARD.clazz():indexConfig.getAnalyzerType().clazz();
		if (indexConfig.getStopWords() !=null) {
			assert (indexConfig.getVersion() !=null);
			try {
				CharArraySet arraySet = new CharArraySet
						(indexConfig.getVersion() ,
								indexConfig.getStopWords() ,
								indexConfig.getIgnoreCase());
				constructor = analyzerClass.getConstructor(new Class []{Version.class, CharArraySet.class});
				if (constructor !=null){
					analyzer = constructor.newInstance(new Object[]{indexConfig.getVersion(), arraySet});
				}
			}catch (Exception ex) {
				throw new RuntimeException (ex);
			}
		}else if(analyzerClass.getConstructor(new Class<?>[]{Version.class})!=null) {
			constructor = analyzerClass.getConstructor(new Class<?>[]{Version.class});
			analyzer = constructor.newInstance(new Object[]{indexConfig.getVersion()});
			assert(constructor !=null);
		}else {
			analyzer = analyzerClass.newInstance();
		}
		assert (analyzer !=null);
		return (Analyzer) analyzer;
	}

	public DAO<Index> getIndexDAO() {
		return indexDAO;
	}

	public void setIndexDAO(DAO<Index> indexDAO) {
		this.indexDAO = indexDAO;
	}

	public LuceneDocumentBuilder<T> getDocumentBuilder() {
		return documentBuilder;
	}

	public void setDocumentBuilder(LuceneDocumentBuilder<T> documentBuilder) {
		this.documentBuilder = documentBuilder;
	}

	@Override
	public void setApplicationContext(ApplicationContext arg0)
			throws BeansException {
		this.applicationContext = arg0;
	}

	public IndexWriter getIndexWriter() {
		return indexWriter;
	}

	public void setIndexWriter(IndexWriter indexWriter) {
		this.indexWriter = indexWriter;
	}

	@Override
	public IndexHolder<IndexWriter> getIndexHolder(Index index) throws IndexServiceException{
		try {
			LuceneIndexWriterHolder holder;
			//			if (indexWriterRef == null) {
			IndexConfig indexConfig = createIndexConfig(index);
			holder = new LuceneIndexWriterHolder();
			holder.setId(index.getId());
			holder.setIndex(index);
			holder.setTargetIndex(createIndexWriter(index,indexConfig));
			holder.setIndexConfig(indexConfig);
			//			} else {
			//				holder = indexWriterRef;
			//			}
			return holder;
		} catch (Exception e) {
			throw new IndexServiceException(e);
		}
	}

	public Boolean getStarted() {
		return started;
	}

}
